'''
Created on Jan 29, 2012

@author: pedro
'''
from Pulses import RCFilteredPulse
from Utils import list_accumulate
from functools import reduce
from math import exp
from matplotlib import pyplot as plt


def stdp(delta_t, tau):
    if delta_t < 0:
        return exp(delta_t/tau)
    elif delta_t > 0:
        return exp(-delta_t/tau)
    else:
        return 0.00        

def spikes_to_epsp(spike_times, synapse, time, dt):
    sum_epsps = []
    
    for item in spike_times:
        epsp = synapse(time,item[0],0.009,0.008,item[1],dt)
        sum_epsps.append(epsp)
    
    sum_epsps = reduce(list_accumulate, sum_epsps)
    
    #Scale the EPSPs
    sum_epsps = map(lambda x: x/2.5, sum_epsps)
    
    #plt.plot(sum_epsps)
    #plt.show()
    return sum_epsps

def alpha_synapse(time,time_fired,tau1,tau2,MaxG,dt):
    timecourse = []
    t = -dt
    has_spiked = False
    for i in range(1,time):
        t += dt
        G = 0.00
        if has_spiked or (time_fired < t+(dt/2) and time_fired > t-(dt/2)):
            t = 0.00 if has_spiked == False else t
            G= MaxG * ( exp(-t/tau1) - exp(-t/tau2) ) / ( tau1 - tau2 ) if tau1 != tau2 else MaxG * (t/tau1) * exp(1 - t/tau1)
            has_spiked = True
        timecourse.append(G)
    return timecourse
    
def rc_synapse(time,time_fired,tau1,tau2,Vmax,dt):
    rc_pulse = RCFilteredPulse(Vmax*100.0,500.0,10.0,7.0)
    rc_pulse.set_on(False)
    t = -dt
    epsp = []
    for i in range(1,time):
        t += dt
        if time_fired < t+(dt/2) and time_fired > t-(dt/2):
            rc_pulse.set_on(True)
        val = next(rc_pulse)
        epsp.append(val)
    return epsp



class Neuron(object):
    
    def __init__(self, v0, Rm, Cm, tau_ref, Vth, V_spike):
        self.v0      = v0                  # initial voltage
        self.Vm      = [v0]                # potential (V) trace over time 
        self.Rm      = Rm                  # resistance (kOhm)
        self.Cm      = Cm                  # capacitance (uF)
        self.tau_m   = Rm*Cm               # time constant (msec)
        self.tau_ref = tau_ref             # refractory period (msec)
        self.Vth     = Vth                 # spike threshold (V)
        self.V_spike = V_spike             # spike delta (V)
        
    def fire(self, pulse, dt):
        raise NotImplementedError("Neuron doesn't implement this function; only subclasses")
    
    def calc_next_vm(self, I, prev_Vm, dt):
        return prev_Vm + (dt/self.tau_m) * ((self.Rm * I) - (prev_Vm - self.v0))

    def reset_vm(self, v0):
        self.Vm = [v0]


class OutputNeuron(Neuron):
    
    def __init__(self, *args):
        super(OutputNeuron, self).__init__(*args)
    
    def fire(self, pulse, dt):
        """Returns firing pattern over given pulse
            
            Keyword arguments:
            pulse -- the current input pulse
            dt    -- time interval for ODE
                
        """
        t = -dt
        time_fired = -1.0
        
        for i in range(1,len(pulse)):
            t += dt
            I = pulse[i]
            val = self.calc_next_vm(I, self.Vm[i-1], dt)
            
            if (val > self.Vth) and (t - time_fired > self.tau_ref):
                time_fired = t
                #print "Time Fired: {0} msec".format(time_fired*1000)
                val = self.Vth + self.V_spike
            elif (t - time_fired <= self.tau_ref):
                val = self.v0
                
            self.Vm.append(val)
            #print "({0},{1}) -- I = {2} -- Vm = {3}".format(i,t,I,self.Vm[i])
            
        return self.Vm[:]
    
    def time_of_fires(self, pulse, dt):
        """Returns all times of neuron firing over given pulse input
            
            @type pulse: float list
            @param pulse: the current input pulse
            @type dt: float 
            @param dt: time interval for ODE
                
            @rtype: float list
            @return: times of fire
        """
        t = -dt
        time_fired = -1.0
        times_fired = []
        
        for i in range(1,len(pulse)):
            t += dt
            I = pulse[i]
            dv = (dt/self.tau_m) * ((self.Rm * I) - (self.Vm[i-1] - self.v0))
            val = self.Vm[i-1] + dv
            
            if (val > self.Vth) and (t - time_fired > self.tau_ref):
                time_fired = t
                val = self.Vth + self.V_spike
                #print "Time Fired: {0} msec".format(time_fired*1000)
                times_fired.append((time_fired, val))
            elif (t - time_fired <= self.tau_ref):
                val = self.v0
                
            self.Vm.append(val)
            #print "({0},{1}) -- I = {2} -- Vm = {3}".format(i,t,I,self.Vm[i])
            
        return times_fired if len(times_fired) > 0 else [(time_fired, 0.0)]




class Interneuron(Neuron):
    
    def __init__(self, v0, Rm, Cm, Vth):
        super(Interneuron, self).__init__(v0, Rm, Cm, 0.00, Vth, 0.00)
        
    def fire(self, pulse, dt):
        t = -dt
        time_fired = -1.0
        
        for i in range(1,len(pulse)):
            t += dt
            I = pulse[i]
            val = self.calc_next_vm(I, self.Vm[i-1], dt)
            
            if (val > self.Vth) and (t - time_fired > self.tau_ref):
                time_fired = t
                val = self.Vth
            elif (t - time_fired <= self.tau_ref):
                val = val
                
            self.Vm.append(val)
            #print "({0},{1}) -- I = {2} -- Vm = {3}".format(i,t,I,self.Vm[i])
            
        return self.Vm[:]


class PyramidalNeuron(Neuron):
    '''
    TODO: PyramidalNeuron classdocs
    '''
    
    def __init__(self, *args):
        super(PyramidalNeuron, self).__init__(*args)
        
    def time_of_fires(self, pulse, dt):
        """Returns all times of neuron firing over given pulse input
            
            @type pulse: float list
            @param pulse: the current input pulse
            @type dt: float 
            @param dt: time interval for ODE
                
            @rtype: float list
            @return: times of fire
        """
        t = -dt
        time_fired = -1.0
        times_fired = []
        
        for i in range(1,len(pulse)):
            t += dt
            I = pulse[i]
            dv = (dt/self.tau_m) * ((self.Rm * I) - (self.Vm[i-1] - self.v0))
            val = self.Vm[i-1] + dv
            
            if (val > self.Vth) and (t - time_fired > self.tau_ref):
                time_fired = t
                val = self.Vth + self.V_spike
                #print "Time Fired: {0} msec".format(time_fired*1000)
                times_fired.append((time_fired, val))
            elif (t - time_fired <= self.tau_ref):
                val = self.v0
                
            self.Vm.append(val)
            #print "({0},{1}) -- I = {2} -- Vm = {3}".format(i,t,I,self.Vm[i])
            
        return times_fired if len(times_fired) > 0 else [(time_fired, 0.0)]
                                                          
    
    def fire(self, pulse, dt):
        """Returns firing pattern over given pulse
            
            Keyword arguments:
            pulse -- the current input pulse
            dt    -- time interval for ODE
                
        """
        t = -dt
        time_fired = -1.0
        
        for i in range(1,len(pulse)):
            t += dt
            I = pulse[i]
            val = self.calc_next_vm(I, self.Vm[i-1], dt)
            
            if (val > self.Vth) and (t - time_fired > self.tau_ref):
                time_fired = t
                #print "Time Fired: {0} msec".format(time_fired*1000)
                val = self.Vth + self.V_spike
            elif (t - time_fired <= self.tau_ref):
                val = self.v0
                
            self.Vm.append(val)
            #print "({0},{1}) -- I = {2} -- Vm = {3}".format(i,t,I,self.Vm[i])
            
        return self.Vm[:]
    
    def first_fire(self, pulse, dt):
        """Returns first time of neuron firing over given pulse input
            
            @type pulse: float list
            @param pulse: the current input pulse
            @type dt: float 
            @param dt: time interval for ODE
                
            @rtype: float
            @return: first time of fire
        """
        t = -dt
        time_fired = -1.0
        
        for i in range(1,len(pulse)):
            t += dt
            I = pulse[i]
            dv = (dt/self.tau_m) * ((self.Rm * I) - (self.Vm[i-1] - self.v0))
            val = self.Vm[i-1] + dv
            
            if (val > self.Vth) and (t - time_fired > self.tau_ref):
                time_fired = t
                val = self.Vth + self.V_spike
                #print "Time Fired: {0} msec".format(time_fired*1000)
                return time_fired
            elif (t - time_fired <= self.tau_ref):
                val = self.v0
                
            self.Vm.append(val)
            #print "({0},{1}) -- I = {2} -- Vm = {3}".format(i,t,I,self.Vm[i])
            
        return time_fired